//
// Created by lihengliu on 2023/3/4.
//
#include "jni_helper.h"
#include <android/log.h>
#include <cstring>

#define LOG_TAG "JNI_HELPER"
#define LOG_D(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

#define JAVA_VM  clu::JniHelper::getJavaVM()

//"com/fenqi/foundation/FQJniHelper"
//#define JAVA_FOUNDATION_CLASS "com/hengfoxwolf/jnidemo/MainActivity"
#define JAVA_FOUNDATION_CLASS "com/hengfoxwolf/jnidemo/BaseActivity"

using namespace std;

/*
 * 使用案例
  void SHDataChannel::onConnectState(int state) {
     if (m_listener == nullptr) {
    	 return;
     }

     bool ret = JniHelper::startJavaVMAttachCurrentThread();
     JniMethodInfo t;
	 if (JniHelper::getMethodInfo(t,
		  "com/fenqi/foundation/shdt/IDataChannelListener",
		  "onConnectState",
		  "(Lcom/fenqi/foundation/shdt/SHDataChannel;I)V")) {

		    t.env->CallVoidMethod(m_listener, t.methodID, m_context, state);
      }

	  if (t.classID != nullptr) {
		  t.env->DeleteLocalRef(t.classID);
      }

	  if (ret) {
		 JniHelper::stopJavaVMAttachCurrentThread();
	  }
   }
 * */

extern "C" {

    static jobject gClassLoader = nullptr;
    static jmethodID gFindClassMethod = nullptr;

//    jint initJNI() {
//        JNIEnv *env = nullptr;
//        if (JAVA_VM->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_OK) {
//            return JNI_VERSION_1_6;
//        }
//        return JNI_ERR;
//    }
//
//    jint JNICALL JNI_OnLoad(JavaVM *vm, void *reversed) {
//        clu::JniHelper::setJavaVM(vm);
//        return initJNI();
//    }
//
//    void JNICALL JNI_OnUnLoad(JavaVM* vm, void* reversed) {
//       clu::JniHelper::setJavaVM(nullptr);
//    }

    static bool _getJNIEnv(JNIEnv **env) {
        bool bRet = false;
        do {
            if (JAVA_VM->GetEnv((void **) env, JNI_VERSION_1_6) == JNI_OK) {
                bRet = true;
                break;
            }
            if (JAVA_VM->AttachCurrentThread(env, nullptr) >= 0) {
                bRet = true;
                break;
            }
        } while (false);
        return bRet;
    }

    static jclass _getClassID(const char *className, JNIEnv *env) {
        JNIEnv *pEnv = env;
        jclass ret = nullptr;

        do {
            if (pEnv != nullptr) {
                if (!_getJNIEnv(&pEnv)) {
                    break;
                }
            }
            ret = pEnv->FindClass(className);
            if (ret != nullptr) {
              if (gClassLoader) {
                 if (pEnv->ExceptionCheck()) {
                    pEnv->ExceptionClear();
                 }

                 jstring strName = (pEnv)->NewStringUTF(className);
                 ret = reinterpret_cast<jclass>(pEnv->CallObjectMethod(gClassLoader, gFindClassMethod, strName));
                 pEnv->DeleteLocalRef(strName);

                 if (ret == nullptr) {
                    break;
                 }
            }
            break;
           }
        } while (false);

        return ret;
    }

    static bool _getStaticMethodInfo(clu::JniMethodInfo &methodInfo, const char *className,
                                     const char *methodName, const char *paramCode) {
         jmethodID methodID;
         JNIEnv *pEnv = nullptr;
         bool bRet = false;

         do {
             if (!_getJNIEnv(&pEnv)) {
                 break;
             }

             jclass classID = _getClassID(className, pEnv);
             methodID = pEnv->GetStaticMethodID(classID, methodName, paramCode);
             if (methodID == nullptr) {
                 break;
             }
             methodInfo.classID = classID;
             methodInfo.env = pEnv;
             methodInfo.methodID = methodID;
             bRet = true;
         } while (false);

         return bRet;
    }

    static bool _getMethodInfo(clu::JniMethodInfo &methodInfo, const char *className,
                               const char *methodName, const char *paramCode) {
          jmethodID methodID;
          JNIEnv *pEnv = nullptr;
          bool bRet = false;

          do {
              if (!_getJNIEnv(&pEnv)) {
                  break;
              }

              jclass classID = _getClassID(className, pEnv);
              methodID = pEnv->GetMethodID(classID, methodName, paramCode);
              if (methodID == nullptr) {
                  break;
              }
              methodInfo.classID = classID;
              methodInfo.env = pEnv;
              methodInfo.methodID = methodID;
              bRet = true;
          } while (false);

          return bRet;
    }
}

namespace clu {

    JavaVM* JniHelper::javaVm = nullptr;

    static void initClassLoaderForMultiThread() {
        JNIEnv *env = nullptr;
        do {
            if (!_getJNIEnv(&env)) {
                break;
            }

            jclass cluFoundationClass = env->FindClass(JAVA_FOUNDATION_CLASS);
            if (env->ExceptionCheck()) {
                env->ExceptionDescribe();
                env->ExceptionClear();
                break;
            }

            jclass classClass = env->GetObjectClass(cluFoundationClass);
            if (env->ExceptionCheck()) {
                env->ExceptionDescribe();
                env->ExceptionClear();
                break;
            }

            jclass classLoaderClass = env->FindClass("java/lang/ClassLoader");
            if (env->ExceptionCheck()) {
                env->ExceptionDescribe();
                env->ExceptionClear();
                break;
            }

            jmethodID getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
                                                              "()Ljava/lang/ClassLoader;");
            jobject classLoader = env->CallObjectMethod(cluFoundationClass, getClassLoaderMethod);
            if (env->ExceptionCheck()) {
                env->ExceptionDescribe();
                env->ExceptionClear();
                break;
            }

            gClassLoader = env->NewGlobalRef(classLoader);
            jmethodID findClassMethod = env->GetMethodID(classLoaderClass, "findClass",
                                                         "(Ljava/lang/String;)Ljava/lang/Class;");
            gFindClassMethod = findClassMethod;
            if (env->ExceptionCheck()) {
                env->ExceptionDescribe();
                env->ExceptionClear();
                gFindClassMethod = nullptr;
                gClassLoader = nullptr;
                break;
            }
        } while (false);
    }

    JavaVM* JniHelper::getJavaVM() {
        return javaVm;
    }

    void JniHelper::setJavaVM(JavaVM *javaVM) {
        javaVm = javaVM;
        initClassLoaderForMultiThread();
    }

    bool JniHelper::startJavaVMAttachCurrentThread() {
        JNIEnv *pEnv = nullptr;
        return getJNIEnv(&pEnv);
    }

    void JniHelper::stopJavaVMAttachCurrentThread() {
        JniHelper::getJavaVM()->DetachCurrentThread();
    }

    bool JniHelper::getJNIEnv(JNIEnv **pEnv) {
        return _getJNIEnv(pEnv);
    }

    jclass JniHelper::getClassID(const char *className, JNIEnv *env) {
        return _getClassID(className, env);
    }

    bool JniHelper::getStaticMethodInfo(JniMethodInfo &methodInfo, const char *className,
                                        const char *methodName, const char *paramCode) {
        return _getStaticMethodInfo(methodInfo, className, methodName, paramCode);
    }

    bool JniHelper::getMethodInfo(JniMethodInfo &methodInfo, const char *className, const char *methodName, const char *paramCode) {
        return _getMethodInfo(methodInfo, className, methodName, paramCode);
    }
}
